//
//  NSString+DDAdd.m
//  AFNetworking
//
//  Created by 许龙 on 2019/9/24.
//

#import "NSString+DDAdd.h"
#import "NSNumber+DDAdd.h"

@implementation NSString (DDAdd)

- (NSString *)stringByURLEncode {
    if ([self respondsToSelector:@selector(stringByAddingPercentEncodingWithAllowedCharacters:)]) {
        /**
         AFNetworking/AFURLRequestSerialization.m
         
         Returns a percent-escaped string following RFC 3986 for a query string key or value.
         RFC 3986 states that the following characters are "reserved" characters.
            - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
            - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
         In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
         query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
         should be percent-escaped in the query string.
            - parameter string: The string to be percent-escaped.
            - returns: The percent-escaped string.
         */
        static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
        static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
        
        NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
        [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
        static NSUInteger const batchSize = 50;
        
        NSUInteger index = 0;
        NSMutableString *escaped = @"".mutableCopy;
        
        while (index < self.length) {
            NSUInteger length = MIN(self.length - index, batchSize);
            NSRange range = NSMakeRange(index, length);
            // To avoid breaking up character sequences such as 👴🏻👮🏽
            range = [self rangeOfComposedCharacterSequencesForRange:range];
            NSString *substring = [self substringWithRange:range];
            NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
            [escaped appendString:encoded];
            
            index += range.length;
        }
        return escaped;
    } else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding);
        NSString *encoded = (__bridge_transfer NSString *)
        CFURLCreateStringByAddingPercentEscapes(
                                                kCFAllocatorDefault,
                                                (__bridge CFStringRef)self,
                                                NULL,
                                                CFSTR("!#$&'()*+,/:;=?@[]"),
                                                cfEncoding);
        return encoded;
#pragma clang diagnostic pop
    }
}

- (NSString *)stringByURLDecode {
    if ([self respondsToSelector:@selector(stringByRemovingPercentEncoding)]) {
        return [self stringByRemovingPercentEncoding];
    } else {
        CFStringEncoding en = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding);
        NSString *decoded = [self stringByReplacingOccurrencesOfString:@"+"
                                                            withString:@" "];
        decoded = (__bridge_transfer NSString *)
        CFURLCreateStringByReplacingPercentEscapesUsingEncoding(
                                                                NULL,
                                                                (__bridge CFStringRef)decoded,
                                                                CFSTR(""),
                                                                en);
        return decoded;
    }
}

- (NSString *)stringByEscapingHTML {
    NSUInteger len = self.length;
    if (!len) return self;
    
    unichar *buf = malloc(sizeof(unichar) * len);
    if (!buf) return self;
    [self getCharacters:buf range:NSMakeRange(0, len)];
    
    NSMutableString *result = [NSMutableString string];
    for (int i = 0; i < len; i++) {
        unichar c = buf[i];
        NSString *esc = nil;
        switch (c) {
            case 34: esc = @"&quot;"; break;
            case 38: esc = @"&amp;"; break;
            case 39: esc = @"&apos;"; break;
            case 60: esc = @"&lt;"; break;
            case 62: esc = @"&gt;"; break;
            default: break;
        }
        if (esc) {
            [result appendString:esc];
        } else {
            CFStringAppendCharacters((CFMutableStringRef)result, &c, 1);
        }
    }
    free(buf);
    return result;
}

#pragma mark - Regular Expression

- (BOOL)fullMatchesRegex:(NSString *)regex {
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    return [pred evaluateWithObject:self];
}

- (BOOL)matchesRegex:(NSString *)regex options:(NSRegularExpressionOptions)options {
    NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:NULL];
    if (!pattern) return NO;
    return ([pattern numberOfMatchesInString:self options:0 range:NSMakeRange(0, self.length)] > 0);
}

- (void)enumerateRegexMatches:(NSString *)regex options:(NSRegularExpressionOptions)options usingBlock:(void (^)(NSString * _Nonnull, NSRange, BOOL * _Nonnull))block {
    if (!regex.length || !block) return;
    NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:NULL];
    if (!pattern) return;
    [pattern enumerateMatchesInString:self options:0 range:NSMakeRange(0, self.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
        block([self substringWithRange:result.range], result.range, stop);
    }];
}

- (BOOL)isNumber {
    NSDecimalNumber *decimal = [NSDecimalNumber decimalNumberWithString:self];
    NSComparisonResult result = [decimal compare:[NSDecimalNumber notANumber]];
    return result != NSOrderedSame;
}

- (BOOL)isChinese {
    return [self fullMatchesRegex:@"[\u4e00-\u9fa5]+"];
}

#pragma mark - Drawing

- (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode {
    CGSize result;
    if (!font) font = [UIFont systemFontOfSize:12];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    if ([self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) {
        NSMutableDictionary *attr = [NSMutableDictionary new];
        attr[NSFontAttributeName] = font;
        if (lineBreakMode != NSLineBreakByWordWrapping) {
            NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
            paragraphStyle.lineBreakMode = lineBreakMode;
            attr[NSParagraphStyleAttributeName] = paragraphStyle;
        }
        CGRect rect = [self boundingRectWithSize:size
                                         options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                                      attributes:attr context:nil];
        result = rect.size;
    } else {
        result = [self sizeWithFont:font constrainedToSize:size lineBreakMode:lineBreakMode];
    }
#pragma clang diagnostic pop
    return result;
}

- (CGFloat)widthForFont:(UIFont *)font {
    CGSize size = [self sizeForFont:font size:CGSizeMake(HUGE, HUGE) mode:NSLineBreakByWordWrapping];
    return size.width;
}

- (CGFloat)heightForFont:(UIFont *)font width:(CGFloat)width {
    CGSize size = [self sizeForFont:font size:CGSizeMake(width, HUGE) mode:NSLineBreakByWordWrapping];
    return size.height;
}

- (NSString *)stringByTrim {
    NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    return [self stringByTrimmingCharactersInSet:set];
}

- (BOOL)isNotBlank {
    NSCharacterSet *blank = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    for (NSInteger i = 0; i < self.length; ++i) {
        unichar c = [self characterAtIndex:i];
        if (![blank characterIsMember:c]) {
            return YES;
        }
    }
    return NO;
}

- (BOOL)containsString:(NSString *)string {
    if (string == nil) return NO;
    return [self rangeOfString:string].location != NSNotFound;
}

- (id)jsonValueDecoded {
    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
    if (!data) {
        return nil;
    }
    __autoreleasing NSError *error = nil;
    id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
    if (error) {
        return nil;
    }
    return result;
}

- (NSRange)rangeOfAll {
    return NSMakeRange(0, self.length);
}

- (NSData *)dataValue {
    return [self dataUsingEncoding:NSUTF8StringEncoding];
}

#pragma mark - Number

- (NSNumber *)numberValue {
    return [NSNumber numberWithString:self];
}

- (NSInteger)decimalDigit {
    if ([self isNumber]) {
        NSArray *array = [self componentsSeparatedByString:@"."];
        if (array.count == 2) {
            NSString *decimalStr = array[1];
            NSInteger i = decimalStr.length - 1;
            for (; i >= 0; i--) {
                NSString *cha = [decimalStr substringWithRange:NSMakeRange(i, 1)];
                if (![@"0" isEqualToString:cha]) {
                    break;
                }
            }
            return i + 1;
        } else {
            return 0;
        }
    }
    return -1;
}

- (NSString *)stringWithDecimalDigit:(NSUInteger)digit {
    return [self stringWithDecimalDigit:digit mode:NSRoundPlain];
}

- (NSString *)stringWithDecimalDigit:(NSUInteger)digit mode:(NSRoundingMode)mode {
    if (![self isNumber]) return nil;
    NSDecimalNumberHandler *handler = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:mode
                                                                                             scale:(short)10
                                                                                  raiseOnExactness:NO
                                                                                   raiseOnOverflow:NO
                                                                                  raiseOnUnderflow:NO
                                                                               raiseOnDivideByZero:NO];
    if (!handler) return self;
    NSDecimalNumber *decimal = [NSDecimalNumber decimalNumberWithString:self];
    decimal = [decimal decimalNumberByRoundingAccordingToBehavior:handler];
    // Fix: Automatic filtering after invalid decimal of 0
    // Example: @"1.5001" format 3 digit will be @"1.5", must be @"1.500"
    NSString *fmt = [NSString stringWithFormat:@"%%.%df", (int)digit];
    return [NSString stringWithFormat:fmt, [decimal doubleValue]];
}

#pragma mark - Calculate

- (NSString *)stringNumberByValue:(NSString *)value
                               op:(DDOperator)op
                            digit:(NSUInteger)digit
                            model:(NSRoundingMode)mode {
    if (![self isNumber] || ![value isNumber]) return nil;
    NSDecimalNumberHandler *handler = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:mode
                                                                                             scale:(short)digit
                                                                                  raiseOnExactness:NO
                                                                                   raiseOnOverflow:NO
                                                                                  raiseOnUnderflow:NO
                                                                               raiseOnDivideByZero:NO];
    if (!handler) return self;
    NSDecimalNumber *d1 = [NSDecimalNumber decimalNumberWithString:self];
    NSDecimalNumber *d2 = [NSDecimalNumber decimalNumberWithString:value];
    NSDecimalNumber *result = nil;
    switch (op) {
        case DDOperatorAdd: result = [d1 decimalNumberByAdding:d2 withBehavior:handler]; break;
        case DDOperatorSubtract: result = [d1 decimalNumberBySubtracting:d2 withBehavior:handler]; break;
        case DDOperatorMultiply: result = [d1 decimalNumberByMultiplyingBy:d2 withBehavior:handler]; break;
        case DDOperatorDivide: result = [d1 decimalNumberByDividingBy:d2 withBehavior:handler]; break;
        case DDOperatorRaisingToPower: result = [d1 decimalNumberByRaisingToPower:[value integerValue] withBehavior:handler]; break;
        case DDOperatorMultiplyingByPowerOf10: result = [d1 decimalNumberByMultiplyingByPowerOf10:(short)[value intValue] withBehavior:handler]; break;
    }
    NSString *fmt = [NSString stringWithFormat:@"%%.%df", (int)digit];
    return [NSString stringWithFormat:fmt, [result doubleValue]];
}

- (NSComparisonResult)stringNumberCompare:(NSString *)value {
    NSDecimalNumber *decimal1 = [NSDecimalNumber decimalNumberWithString:self];
    NSDecimalNumber *decimal2 = [NSDecimalNumber decimalNumberWithString:value];
    return [decimal1 compare:decimal2];
}

- (NSString *)stringByDelimiter {
    if (![self isNumber]) return self;
    
    NSString *v = self;
    BOOL flag = [self hasPrefix:@"+"];
    if (flag) v = [v substringFromIndex:1];
    
    NSNumber *number = [NSNumber numberWithString:v];
    if (number == nil) {
        return self;
    }
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    formatter.numberStyle = NSNumberFormatterCurrencyStyle;
    formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];

    formatter.minimumFractionDigits = 2;
    formatter.maximumFractionDigits = 2;
    
    NSString *textNumber = [formatter stringFromNumber:number];
    if (textNumber.length) {
        NSRange range = [textNumber rangeOfString:@"$"];
        if (range.location != NSNotFound)  {
            textNumber = [textNumber stringByReplacingCharactersInRange:range withString:@""];
        }
    } else {
        return self;
    }
    if (flag) {
        textNumber = [NSString stringWithFormat:@"+%@",textNumber];
    }
    return textNumber;
}

- (nullable NSString *)stringWithDecimalDigit:(NSUInteger)digit up:(BOOL)up {
    CGFloat powValue = pow(10, digit);
    CGFloat result = 0;
    if (up) {
        result = ceil([self doubleValue] * powValue) / powValue;
    } else {
        result = floor([self doubleValue] * powValue) / powValue;
    }
    NSString *fmt = [NSString stringWithFormat:@"%%.%df", (int)digit];
    return [NSString stringWithFormat:fmt, result];
}

@end
